using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Office.Server;
using Microsoft.Office.Server.Administration;
using Microsoft.Office.Server.UserProfiles;
using Microsoft.SharePoint;
using System.Web;
using System.Xml;
using Winsmarts.PPI.Common;

using System.Reflection;

namespace ProfilePropertyMgr
{
    internal static class WorkerBee
    {
        internal static void ExportProfiles(ProgramInputs inputs, UserProfileManager profileManager)
        {
            // Variable Naming convention: <objectinConcern><Property/Properties>

            PropertyCollection profileProperties = profileManager.PropertiesWithSection;

            XmlDocument exportProperties = new XmlDocument();
            XmlNode nodeProperties = exportProperties.CreateNode(XmlNodeType.Element, "Properties", "");
            exportProperties.AppendChild(nodeProperties);

            PropertyInfo[] allProps = typeof(Property).GetProperties();
            Dictionary<string, PropertyInfo> typeProperties = new Dictionary<string, PropertyInfo>();
            foreach (PropertyInfo typeProperty in allProps)
            {
                typeProperties.Add(typeProperty.Name, typeProperty);
            }

            foreach (Property profileProperty in profileProperties)
            {
                // Should I be even exporting this property?
                bool isExportable = ShouldExport(typeProperties["Name"], profileProperty, true);

                if (isExportable || profileProperty.Type.Equals("section"))
                {
                    //First part for properties. Second part for sections)
                    if (!profileProperty.Type.Equals("section"))
                    {
                        XmlNode nodeProperty = exportProperties.CreateNode(XmlNodeType.Element, "Property", "");
                        nodeProperties.AppendChild(nodeProperty);

                        foreach (PropertyInfo typeProperty in typeProperties.Values)
                        {
                            if (ShouldExport(typeProperty, profileProperty, false))
                            {
                                XmlNode nodePropertyInfo = exportProperties.CreateNode(XmlNodeType.Element, "PropertyInfo", "");

                                XmlAttribute attribProfilePropInfo;
                                object reflectedValue;

                                //Name
                                attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Name", "") as XmlAttribute;
                                attribProfilePropInfo.Value = typeProperty.Name;
                                nodePropertyInfo.Attributes.Append(attribProfilePropInfo);

                                // Data Type
                                attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Type", "") as XmlAttribute;
                                attribProfilePropInfo.Value = typeProperty.PropertyType.AssemblyQualifiedName;
                                nodePropertyInfo.Attributes.Append(attribProfilePropInfo);

                                // Value
                                if (typeProperty.PropertyType.FullName.Equals("Microsoft.Office.Server.UserProfiles.LocalizedStringManager"))
                                {
                                    LocalizedStringManager sm = (LocalizedStringManager)typeProperty.GetValue(profileProperty, null);
                                    int[] languages = sm.GetUniqueLcids();
                                    foreach (int lang in languages)
                                    {
                                        XmlNode nodeDataLang = exportProperties.CreateNode(XmlNodeType.Element, "Data", "");

                                        attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Language", "") as XmlAttribute;
                                        attribProfilePropInfo.Value = lang.ToString();
                                        nodeDataLang.Attributes.Append(attribProfilePropInfo);

                                        attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Data", "") as XmlAttribute;
                                        attribProfilePropInfo.Value = sm[lang];
                                        nodeDataLang.Attributes.Append(attribProfilePropInfo);

                                        nodePropertyInfo.AppendChild(nodeDataLang);
                                    }
                                }
                                else
                                {
                                    attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Data", "") as XmlAttribute;
                                    reflectedValue = typeProperty.GetValue(profileProperty, null);
                                    attribProfilePropInfo.Value = (reflectedValue == null) ? String.Empty : reflectedValue.ToString();
                                    nodePropertyInfo.Attributes.Append(attribProfilePropInfo);
                                }
                                nodeProperty.AppendChild(nodePropertyInfo);
                            }
                        }


                        // lets see if this prop has values
                        if (profileProperty.ChoiceType == ChoiceTypes.Closed || profileProperty.ChoiceType == ChoiceTypes.Open)
                        {
                            // double check choice list
                            if (profileProperty.ChoiceList != null)
                            {
                                XmlElement oChoicesNode = exportProperties.CreateElement("Choices");
                                nodeProperty.AppendChild(oChoicesNode);

                                foreach (string sChoice in profileProperty.ChoiceList.GetAllTerms(false))
                                {
                                    XmlElement oChoiceNode = exportProperties.CreateElement("Choice");
                                    oChoicesNode.AppendChild(oChoiceNode);

                                    oChoiceNode.InnerText = sChoice;
                                }
                            }
                        }
                    }
                    else
                    {
                        XmlNode nodeProperty = exportProperties.CreateNode(XmlNodeType.Element, "Section", "");
                        nodeProperties.AppendChild(nodeProperty);
                        foreach (string propName in new string[] { "Name", "DisplayName", "DisplayNameLocalized", "DisplayOrder" })
                        {
                            PropertyInfo typeProperty = typeProperties[propName];
                            XmlNode nodePropertyInfo = exportProperties.CreateNode(XmlNodeType.Element, "SectionInfo", "");

                            XmlAttribute attribProfilePropInfo;
                            object reflectedValue;

                            //Name
                            attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Name", "") as XmlAttribute;
                            attribProfilePropInfo.Value = typeProperty.Name;
                            nodePropertyInfo.Attributes.Append(attribProfilePropInfo);

                            // Data Type
                            attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Type", "") as XmlAttribute;
                            attribProfilePropInfo.Value = typeProperty.PropertyType.AssemblyQualifiedName;
                            nodePropertyInfo.Attributes.Append(attribProfilePropInfo);

                            // Value
                            if (typeProperty.PropertyType.FullName.Equals("Microsoft.Office.Server.UserProfiles.LocalizedStringManager"))
                            {
                                LocalizedStringManager sm = (LocalizedStringManager)typeProperty.GetValue(profileProperty, null);
                                int[] languages = sm.GetUniqueLcids();
                                foreach (int lang in languages)
                                {
                                    XmlNode nodeDataLang = exportProperties.CreateNode(XmlNodeType.Element, "Data", "");

                                    attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Language", "") as XmlAttribute;
                                    attribProfilePropInfo.Value = lang.ToString();
                                    nodeDataLang.Attributes.Append(attribProfilePropInfo);

                                    attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Data", "") as XmlAttribute;
                                    attribProfilePropInfo.Value = sm[lang];
                                    nodeDataLang.Attributes.Append(attribProfilePropInfo);

                                    nodePropertyInfo.AppendChild(nodeDataLang);
                                }
                            }
                            else
                            {
                                attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Data", "") as XmlAttribute;
                                reflectedValue = typeProperty.GetValue(profileProperty, null);
                                attribProfilePropInfo.Value = (reflectedValue == null) ? String.Empty : reflectedValue.ToString();
                                nodePropertyInfo.Attributes.Append(attribProfilePropInfo);
                            }
                            nodeProperty.AppendChild(nodePropertyInfo);
                        }

                    }
                }
            }

            exportProperties.Save(inputs.FileName);
        }

        internal static void ImportProfiles(ProgramInputs inputs, UserProfileManager profileManager)
        {
            Dictionary<string, int> orderProperties = new Dictionary<string, int>();
            XmlDocument docImportProperties = new XmlDocument();
            docImportProperties.Load(inputs.FileName);

            PropertyInfo[] typeProperties = typeof(Property).GetProperties();
            //Create an enumerable dictionary of this - will help afterwards.
            Dictionary<string, PropertyInfo> reflectedProperties = new Dictionary<string, PropertyInfo>();
            foreach (PropertyInfo typeProperty in typeProperties)
            {
                reflectedProperties.Add(typeProperty.Name, typeProperty);
            }
            PropertyCollection pc = profileManager.PropertiesWithSection;

            XmlNodeList importProperties = docImportProperties.SelectNodes("./Properties/Section");
            foreach (XmlNode importProperty in importProperties)
            {
                bool created = false;
                string currPropName = importProperty.SelectSingleNode("./SectionInfo[@Name='Name']").Attributes["Data"].Value;
                Property p = pc.GetSectionByName(currPropName);
                if (p == null)
                {
                    p = pc.Create(true);
                    created = true;
                    if (currPropName.StartsWith("SPS-"))
                        currPropName = "OWN-" + currPropName.Substring(4);
                }
                foreach (XmlNode sectInfoNode in importProperty.SelectNodes("./SectionInfo")) // modified - make sure only picks up PropertyInfo nodes and not Choices node (if present)
                {
                    UpdatePropertyOfProperty(orderProperties, reflectedProperties, created, currPropName, p, sectInfoNode);
                }

                // end of edit
                if (created)
                    pc.Add(p);
                else
                    p.Commit();

            }


            //Set the order correctly. This cannot be done during the previous loop because you cannot set the prder for properties which do not exists yet :).
            foreach (KeyValuePair<string, int> pair in orderProperties)
            {
                pc.SetDisplayOrderBySectionName(pair.Key, pair.Value);
            }

            orderProperties.Clear();

            importProperties = docImportProperties.SelectNodes("./Properties/Property");

            foreach (XmlNode importProperty in importProperties)
            {
                bool created = false;
                string currPropName = importProperty.SelectSingleNode("./PropertyInfo[@Name='Name']").Attributes["Data"].Value;
                Property p = pc.GetPropertyByName(currPropName);
                if (p == null)
                {
                    p = pc.Create(false);
                    created = true;
                }

                foreach (XmlNode propInfoNode in importProperty.SelectNodes("./PropertyInfo")) // modified - make sure only picks up PropertyInfo nodes and not Choices node (if present)
                {
                    UpdatePropertyOfProperty(orderProperties, reflectedProperties, created, currPropName, p, propInfoNode);
                }

                // ok, we're done with the properties, now check to see if we need multi-choice

                // lets see if this prop has values
                if (p.ChoiceType == ChoiceTypes.Closed || p.ChoiceType == ChoiceTypes.Open)
                {
                    // now lets check to see if we have nodes for it

                    XmlNode oChoicesNode = importProperty.SelectSingleNode("Choices");

                    if (oChoicesNode != null)
                    {
                        foreach (XmlNode oChoiceNode in oChoicesNode.ChildNodes)
                        {
                            p.ChoiceList.Add(oChoiceNode.InnerText);
                        }
                    }
                }

                // end of edit
                if (created)
                    pc.Add(p);
                else
                    p.Commit();
            }


            //Set the order correctly. This cannot be done during the previous loop because you cannot set the order for sections which do not exists yet :).
            foreach (KeyValuePair<string, int> pair in orderProperties)
            {
                pc.SetDisplayOrderByPropertyName(pair.Key, pair.Value);
            }
            pc.CommitDisplayOrder();
        }

        private static void UpdatePropertyOfProperty(Dictionary<string, int> orderProperties, Dictionary<string, PropertyInfo> reflectedProperties, bool created, string currPropName, Property p, XmlNode infoNode)
        {
            PropertyInfo relevantPropertyInfo = reflectedProperties[infoNode.Attributes["Name"].Value];
            // object importPropertyValue = Activator.CreateInstance(propType);

            if (ShouldUpdateField(relevantPropertyInfo) || created)
            {
                Type propType = Type.GetType(infoNode.Attributes["Type"].Value);
                if (propType.IsEnum)
                {
                    object importPropertyValue = Enum.Parse(propType, infoNode.Attributes["Data"].Value);
                    relevantPropertyInfo.SetValue(p, importPropertyValue, null);
                }
                else if (propType.FullName.Equals("Microsoft.Office.Server.UserProfiles.LocalizedStringManager"))
                {
                    LocalizedStringManager sm = (LocalizedStringManager)relevantPropertyInfo.GetValue(p, null);
                    
                    sm.ClearAllButDefaultLanguage();

                    foreach (XmlNode propLangDataNode in infoNode.SelectNodes("./Data"))
                    {
                        int lang = Int32.Parse(propLangDataNode.Attributes["Language"].Value);
                        string val = propLangDataNode.Attributes["Data"].Value;
                        sm[lang] = val;
                    }
                }
                else if (relevantPropertyInfo.Name.Equals("DisplayOrder"))
                {
                    orderProperties.Add(currPropName, (int)Convert.ChangeType(infoNode.Attributes["Data"].Value, propType));
                }
                else
                {

                    object importPropertyValue = Convert.ChangeType(infoNode.Attributes["Data"].Value, propType);
                    if (created && relevantPropertyInfo.Name.Equals("Name") && importPropertyValue.ToString().StartsWith("SPS-"))
                    {
                        importPropertyValue = currPropName;
                    }

                    relevantPropertyInfo.SetValue(p, importPropertyValue, null);
                }
            }
        }

        //Fields to skip during import when updating
        private static string[] fieldsToSkip = new string[] { "Name", "Type", "Length", "IsMultivalued" };
        /// <summary>
        /// This method returns true for the field which can be imported when updating
        /// </summary>
        /// <param name="typeProperty"></param>
        /// <param name="profileProperty"></param>
        /// <param name="entireProperty"></param>
        /// <returns></returns>
        private static bool ShouldUpdateField(PropertyInfo typeProperty)
        {
            if ((typeProperty.CanWrite ||
                typeProperty.PropertyType.FullName.Equals("Microsoft.Office.Server.UserProfiles.LocalizedStringManager") ||
                typeProperty.Name.Equals("DisplayOrder"))
                && !Array.Exists<string>(fieldsToSkip, v => v.Equals(typeProperty.Name)))
            {
                return true;
            }
            else return false;
        }


        // In English, the below method tells me to export only properties that are both read/write,
        // That are valuetypes or string, exclude UserProfile_GUID and whatever starts with SPS-. 
        // SPS- is reserved for sharepoint, and UserProfile_GUID cannot be deleted/added.
        private static bool ShouldExport(PropertyInfo typeProperty, Property profileProperty, bool entireProperty)
        {
            bool toReturn = false;

            if (typeProperty.CanRead && !typeProperty.CanWrite &&
                (typeProperty.PropertyType.FullName.Equals("Microsoft.Office.Server.UserProfiles.LocalizedStringManager") ||
                 typeProperty.Name.Equals("DisplayOrder"))
                )
            {
                toReturn = true;
            }
            else
            {
                toReturn = typeProperty.CanRead & typeProperty.CanWrite &
                            (
                                typeProperty.PropertyType.IsValueType |
                                typeProperty.PropertyType.FullName.Equals("System.String")

                            );
            }

            // Proceed only if toReturn is still true ;
            // Also, the following applies to only entire properties
            if (toReturn & entireProperty)
            {
                object reflectedValue = typeProperty.GetValue(profileProperty, null);
                string strReflectedValue = (reflectedValue == null) ? String.Empty : reflectedValue.ToString();
                toReturn &= !strReflectedValue.Equals("UserProfile_GUID");
                toReturn &= !strReflectedValue.StartsWith("SPS-");
            }
            return toReturn;
        }

    }
}
